﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Materials;

namespace Beaker.OpenCube.Render.Shapes
{
	public sealed class Box
		: Shape
	{
		private readonly Point3 _corner1;
		private readonly Point3 _corner2;
		private readonly IMaterial _material;

		public Box( Point3 corner1, Point3 corner2, IMaterial material )
		{
			_corner1 =
				new Point3(
					Math.Min( corner1.X, corner2.X ),
					Math.Min( corner1.Y, corner2.Y ),
					Math.Min( corner1.Z, corner2.Z )
				);
			_corner2 =
				new Point3(
					Math.Max( corner1.X, corner2.X ),
					Math.Max( corner1.Y, corner2.Y ),
					Math.Max( corner1.Z, corner2.Z )
				);
			_material = material;
		}

		/// <summary>
		/// Computes the intersection with the given ray.
		/// </summary>
		/// <param name="scene">The scene.</param>
		/// <param name="ray">The ray to compute an intersection with.</param>
		/// <returns>
		/// An intersection or <c>null</c> when there was no intersection.
		/// </returns>
		public override Intersection ComputeIntersection( Scene scene, Ray ray )
		{
			double ox = ray.Origin.X;
			double oy = ray.Origin.Y;
			double oz = ray.Origin.Z;
			double dx = ray.Direction.X;
			double dy = ray.Direction.Y;
			double dz = ray.Direction.Z;

			double txMin, tyMin, tzMin;
			double txMax, tyMax, tzMax;

			int faceIn, faceOut;

			double a = 1d / dx;
			if( a >= 0 )
			{
				txMin = ( _corner1.X - ox ) * a;
				txMax = ( _corner2.X - ox ) * a;
			}
			else
			{
				txMin = ( _corner2.X - ox ) * a;
				txMax = ( _corner1.X - ox ) * a;
			}

			double b = 1d / dy;
			if( b >= 0 )
			{
				tyMin = ( _corner1.Y - oy ) * b;
				tyMax = ( _corner2.Y - oy ) * b;
			}
			else
			{
				tyMin = ( _corner2.Y - oy ) * b;
				tyMax = ( _corner1.Y - oy ) * b;
			}

			double c = 1d / dz;
			if( c >= 0 )
			{
				tzMin = ( _corner1.Z - oz ) * c;
				tzMax = ( _corner2.Z - oz ) * c;
			}
			else
			{
				tzMin = ( _corner2.Z - oz ) * c;
				tzMax = ( _corner1.Z - oz ) * c;
			}

			double t0, t1;

			// find largest entering t value

			if( txMin > tyMin )
			{
				t0 = txMin;
				faceIn = ( a >= 0.0 ) ? 0 : 3;
			}
			else
			{
				t0 = tyMin;
				faceIn = ( b >= 0.0 ) ? 1 : 4;
			}

			if( tzMin > t0 )
			{
				t0 = tzMin;
				faceIn = ( c >= 0.0 ) ? 2 : 5;
			}

			// find smallest exiting t value

			if( txMax < tyMax )
			{
				t1 = txMax;
				faceOut = ( a >= 0.0 ) ? 3 : 0;
			}
			else
			{
				t1 = tyMax;
				faceOut = ( b >= 0.0 ) ? 4 : 1;
			}

			if( tzMax < t1 )
			{
				t1 = tzMax;
				faceOut = ( c >= 0.0 ) ? 5 : 2;
			}

			if( t0 < t1 && t1 > 0d ) // > Double.Epsilon )
			{
				double tMin;
				int face;

				if( t0 > 0d ) // Double.Epsilon )
				{
					tMin = t0;  			// ray hits outside surface
					face = faceIn;
				}
				else
				{
					tMin = t1;				// ray hits inside surface
					face = faceOut;
				}

				return
					new Intersection( scene, this, ray, tMin, FaceNormals[face], _material );
			}

			return null;
		}

		private static readonly Vector3[] FaceNormals = new[]
		{
			new Vector3( -1, 0, 0 ),
			new Vector3( 0, -1, 0 ),
			new Vector3( 0, 0, -1 ),
			new Vector3( 1, 0, 0 ),
			new Vector3( 0, 1, 0 ),
			new Vector3( 0, 0, 1 )			
		};
	}

}
